// Rover Metallic - dense static micro-facet reflections.
// Two static procedural facet layers create many small view/light-dependent
// reflections without animation or a broad white overlay.
#include "RoverMetallic_C3009_armdown_Parameters.hlsl"

cbuffer cb4 : register(b4) { float4 cb4[177]; }
cbuffer cb3 : register(b3) { float4 cb3[43]; }
cbuffer cb2 : register(b2) { float4 cb2[43]; }
cbuffer cb1 : register(b1) { float4 cb1[4]; }
cbuffer cb0 : register(b0) { float4 cb0[283]; }

float RoverMetallicHash12(float2 p)
{
    float3 p3 = frac(float3(p.xyx) * 0.1031);
    p3 += dot(p3, p3.yzx + 33.33);
    return frac((p3.x + p3.y) * p3.z);
}

float2 RoverMetallicHash22(float2 p)
{
    float n = RoverMetallicHash12(p);
    return frac(float2(n, RoverMetallicHash12(p + 17.17)) * float2(1.0, 1.6180339));
}

float3 RoverMetallicWorldPosition(float4 screenPosition)
{
    float4 projected = cb0[45] * screenPosition.y;
    projected += cb0[44] * screenPosition.x;
    projected += cb0[46] * screenPosition.z;
    projected += cb0[47];
    return projected.xyz / max(abs(projected.w), 0.0001);
}

float3 RoverMetallicLightDirection(float3 worldPosition, float2 texcoord1, float texcoord4X)
{
    float3 directionalLight = (cb3[23].xyz - cb3[42].xyz) * cb4[129].w;
    if (cb4[129].y >= 0.5) directionalLight += cb3[42].xyz;
    directionalLight = normalize(directionalLight);

    float3 relativePosition = worldPosition - cb0[70].xyz;
    float3 lightPoint = cb2[5].xyz + float3(0.0, 0.0, 100.0);
    float3 lightToCamera = cb1[0].xyz - lightPoint;
    float3 surfaceToCamera = cb1[0].xyz - relativePosition;
    float3 localLight = normalize(lerp(lightToCamera, surfaceToCamera, cb2[30].y));

    float distanceBlend = saturate(cb1[0].w * length(lightPoint - cb1[0].xyz));
    uint lightType = (asuint(cb1[1].w) >> 16) & 3u;
    float isTypeOne = lightType == 1u ? 1.0 : 0.0;
    float isTypeTwo = lightType == 2u ? 1.0 : 0.0;
    float typeBlend = isTypeTwo * (1.0 - isTypeOne * 0.5) + isTypeOne * 0.5;

    float3 surfaceRay = normalize(surfaceToCamera);
    float3 lightAxis = normalize(cb1[1].xyz);
    float cone = saturate((dot(surfaceRay, lightAxis) - cb1[2].x) * cb1[2].y);
    float shapedDistance = min(1.0, 1.0 + distanceBlend - cone * cone);
    float useCone = max(0.0, typeBlend * 2.0 - 1.0);
    float localBlend = lerp(distanceBlend, shapedDistance, useCone);

    float3 lightDirection = lerp(localLight, directionalLight, localBlend);
    lightDirection = lerp(directionalLight, lightDirection, cb1[3].z);
    float3 interpolatedLight = float3(texcoord1, texcoord4X);
    lightDirection = lerp(lightDirection, interpolatedLight, saturate(cb2[27].y));
    return normalize(lightDirection);
}

float RoverMetallicFacetMask(
    float2 uv, float density, float radius, float stretch, float2 layerOffset,
    out float2 cellId, out float2 cellRandom)
{
    float2 grid = uv * density + layerOffset;
    cellId = floor(grid);
    float2 p = frac(grid) - 0.5;
    cellRandom = RoverMetallicHash22(cellId + layerOffset * 13.7);

    float2 centerOffset = (cellRandom - 0.5) * RM_CELL_JITTER;
    p -= centerOffset;

    float angle = RoverMetallicHash12(cellId + 37.21 + layerOffset) * 6.2831853;
    float s = sin(angle), c = cos(angle);
    float2 q = float2(c * p.x + s * p.y, -s * p.x + c * p.y);

    float streakRnd = RoverMetallicHash12(cellId + 73.11 + layerOffset * 1.3);
    float extraStretch = 1.0 + RM_DIRECTIONAL_STREAKINESS * lerp(0.55, 1.65, streakRnd);
    q.x /= max(stretch * extraStretch, 0.05);

    float warpPhaseA = RoverMetallicHash12(cellId + 81.17 + layerOffset * 4.1) * 6.2831853;
    float warpPhaseB = RoverMetallicHash12(cellId + 96.43 + layerOffset * 2.9) * 6.2831853;
    float warpA = sin(q.y * RM_SHAPE_WARP_FREQUENCY + warpPhaseA);
    float warpB = sin(q.x * (RM_SHAPE_WARP_FREQUENCY * 0.65) + warpPhaseB);
    q.y += warpA * RM_SHAPE_WARP_STRENGTH * 0.18;
    q.x += warpB * RM_SHAPE_WARP_STRENGTH * 0.12;

    float sizeRnd = RoverMetallicHash12(cellId + 53.19 + layerOffset * 2.7);
    float sizeScale = lerp(1.0 - RM_SIZE_VARIATION, 1.0 + RM_SIZE_VARIATION, sizeRnd);
    float r = max(0.01, radius * sizeScale);
    float d = length(q);

    // Soft facet body plus a tighter core. This gives each reflection enough
    // screen area to remain visible without becoming a broad surface overlay.
    float body = 1.0 - smoothstep(r * RM_EDGE_SOFTNESS, r, d);
    float core = 1.0 - smoothstep(r * 0.20, r * 0.58, d);
    return saturate(body * RM_FACET_BODY_WEIGHT + core * RM_FACET_CORE_WEIGHT);
}

float3 RoverMetallicMicroNormal(float3 normal, float4 tangentInput, float2 cellId, float2 salt)
{
    float3 N = normalize(normal);
    float3 T = tangentInput.xyz - N * dot(tangentInput.xyz, N);
    float tLen = length(T);
    if (tLen < 0.0001) return N;
    T /= tLen;

    float handedness = tangentInput.w < 0.0 ? -1.0 : 1.0;
    float3 B = cross(N, T) * handedness;
    float bLen = length(B);
    if (bLen < 0.0001) return N;
    B /= bLen;

    float2 tiltRandom = RoverMetallicHash22(cellId + salt) * 2.0 - 1.0;
    float tiltScale = lerp(0.55, 1.0, RoverMetallicHash12(cellId + salt * 0.37));
    float2 tilt = tiltRandom * RM_NORMAL_VARIATION * tiltScale;
    return normalize(N + T * tilt.x + B * tilt.y);
}

float RoverMetallicFacetResponse(
    float3 microNormal, float3 viewDirection, float3 lightDirection,
    float3 halfDirection, float randomIntensity)
{
    float ndh = saturate(dot(microNormal, halfDirection));
    float ndv = saturate(abs(dot(microNormal, viewDirection)));
    float ndl = saturate(dot(microNormal, lightDirection));

    // Much wider than the previous 96-power lobe. The wide term makes many
    // facets readable, while the core still produces distinctly bright glints.
    float wideSpec = pow(ndh, RM_SPECULAR_WIDE_POWER) * RM_SPECULAR_WIDE_STRENGTH;
    float coreSpec = pow(ndh, RM_SPECULAR_CORE_POWER) * RM_SPECULAR_CORE_STRENGTH;

    // Camera-facing micro-facets provide the characteristic small moving metal
    // flashes even when the game's recovered light vector is not perfectly aligned.
    float viewGlint = pow(ndv, RM_VIEW_GLINT_POWER) * RM_VIEW_GLINT_STRENGTH;
    float lightResponse = lerp(RM_MIN_LIGHT_RESPONSE, 1.0, ndl);

    return (wideSpec + coreSpec + viewGlint) * lightResponse * randomIntensity;
}

void main(
    linear centroid float4 v0 : TEXCOORD10,
    linear centroid float4 v1 : TEXCOORD11,
    float2 v2 : TEXCOORD0,
    float2 v3 : TEXCOORD1,
    float2 v4 : TEXCOORD2,
    float2 v5 : TEXCOORD3,
    float2 v6 : TEXCOORD4,
    float4 v7 : VELOCITY_PREV_POS0,
    float4 v8 : SV_Position0,
    uint v9 : SV_IsFrontFace0,
    out float4 o0 : SV_Target0,
    out float4 o3 : SV_Target3)
{
    float3 worldPosition = RoverMetallicWorldPosition(v8);
    float3 viewDirection = normalize(-worldPosition);
    float3 normal = normalize(v1.xyz);
    if (v9 == 0) normal = -normal;

    float3 lightDirection = RoverMetallicLightDirection(worldPosition, v3, v6.x);
    float3 halfDirection = normalize(viewDirection + lightDirection);

    float2 metallicUV = v2 * RM_PATTERN_SCALE;

    float2 idA, rndA, idB, rndB;
    float maskA = RoverMetallicFacetMask(
        metallicUV, RM_PRIMARY_DENSITY, RM_PRIMARY_SIZE, RM_PRIMARY_STRETCH,
        float2(0.0, 0.0), idA, rndA);
    float maskB = RoverMetallicFacetMask(
        metallicUV, RM_SECONDARY_DENSITY, RM_SECONDARY_SIZE, RM_SECONDARY_STRETCH,
        float2(19.37, 7.11), idB, rndB);

    float3 nA = RoverMetallicMicroNormal(normal, v0, idA, float2(121.31, 17.73));
    float3 nB = RoverMetallicMicroNormal(normal, v0, idB, float2(47.19, 203.53));

    float randA = lerp(1.0 - RM_INTENSITY_VARIATION, 1.0 + RM_INTENSITY_VARIATION,
                       RoverMetallicHash12(idA + 211.79));
    float randB = lerp(1.0 - RM_INTENSITY_VARIATION, 1.0 + RM_INTENSITY_VARIATION,
                       RoverMetallicHash12(idB + 91.43));

    float responseA = RoverMetallicFacetResponse(
        nA, viewDirection, lightDirection, halfDirection, randA);
    float responseB = RoverMetallicFacetResponse(
        nB, viewDirection, lightDirection, halfDirection, randB);

    float primary = maskA * responseA * RM_PRIMARY_STRENGTH;
    float secondary = maskB * responseB * RM_SECONDARY_STRENGTH;
    float reflection = primary + secondary;

    // Tiny continuous Fresnel cue only. It is intentionally too weak to create
    // the white-film problem of the first metallic implementation.
    float ndvBase = saturate(abs(dot(normal, viewDirection)));
    float fresnel = pow(saturate(1.0 - ndvBase), RM_BASE_FRESNEL_POWER)
        * RM_BASE_FRESNEL_STRENGTH;

    float3 finalHighlight = RM_METAL_COLOR * (reflection + fresnel);
    finalHighlight += float3(1.0, 1.0, 1.0) * reflection * RM_WHITE_CORE;

    o0 = float4(max(finalHighlight, 0.0), 0.0);
    o3 = float4(0.0, 0.0, 0.0, 0.0);
}
